package samsung.entities;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import samsung.exceptions.BrokenCountryBorderException;
import samsung.exceptions.NotConnectedCountriesException;
import samsung.exceptions.OutOfBorderException;
import samsung.other.Position;

/**
 * Class <code>Area</code> describe area entity that contains defined 
 * quantity of countries. (Current class describe one test case of working
 * program).
 *
 * @author Vitaliy Muzalevskiy
 */
public class Area {

    public static final int AREA_START_POINT_BY_LENGTH = 1;
    public static final int AREA_START_POINT_BY_HEIGHT = 1;
    public static final int AREA_END_POINT_BY_LENGTH = 10;
    public static final int AREA_END_POINT_BY_HEIGHT = 10;
    private List<Country> countries = new ArrayList<Country>();
    private Map<Position, City> members = new HashMap<Position, City>();
    private Map<String, Integer> results = 
            new TreeMap<String, Integer>(new ResultComparator());
    private boolean finish;
    
    /**
     * Class <code>ResultComparator</code> implements <code>Comparator</code>. 
     * Need to compare values of result by iteration count value, if it same
     * for some countries, they compared by country name.
     */
    private class ResultComparator implements Comparator<String>{

        @Override
        public int compare(String countryName1, String countryName2) {
            int value1 = getCountryByName(countryName1).getLifeCounter();
            int value2 = getCountryByName(countryName2).getLifeCounter();
            if(value1 == value2){
                return countryName1.compareTo(countryName2);
            } else {
                return value1 - value2;
            }
        }
        
    }
    
    private Country getCountryByName(String name){
        Country result = null;
        for(Country country: countries){
            if(country.getName().equals(name)){
                result = country;
            }
        }
        return result;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 17 * hash + Objects.hashCode(this.countries);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Area other = (Area) obj;
        if (!Objects.equals(this.countries, other.countries)) {
            return false;
        }
        return true;
    }

    public void makeDissemination() {
        for (City member : members.values()) {
            member.giveMoney();
            if (!member.isFinish()) {
                member.finishDissemination();
            }
        }
        for (Country country : countries) {
            if (!country.isFinish()) {
                country.increaseLifeCounter();
                if (country.finishDissemination()) {
                    results.put(country.getName(), country.getLifeCounter());
                }
            }
        }
    }

    /**
     * Method check current area for connections between all countries in
     * current area.
     *
     * @throws NotConnectedCountriesException - if there is no connection with
     * some countries.
     */
    public void checkAreaOnCountryConnections()
            throws NotConnectedCountriesException {
        Set<String> countriesNames = new HashSet<String>();
        if (countries.size() > 1) {
            for (City member : members.values()) {
                for (City neighbor : member.getNeighbors()) {
                    if (!member.getNativeCountryName()
                            .equals(neighbor.getNativeCountryName())) {
                        countriesNames.add(neighbor.getNativeCountryName());
                    }
                }
            }
            if (countriesNames.size() != countries.size()) {
                throw new NotConnectedCountriesException();
            }
        }
    }

    /**
     * Method add country to the area countries list.
     *
     * @param country - value of <code>Country</code> type that need to be added
     * to current area country list.
     * @throws OutOfBorderException - if location of country is out of border of
     * current area.
     */
    public void addCountry(Country country) throws OutOfBorderException {
        if (dontFitAreaBorders(country)) {
            throw new OutOfBorderException();
        } else {
            countries.add(country);
        }
    }

    /**
     * Method find all cities in current area, add them to the area members
     * list, set the start capital of each member in accordance to there native
     * countries and define neighbors of each city.
     *
     * @throws BrokenCountryBorderException - if some countries have the same
     * part of area.
     */
    public void prepareMembersToWork() throws BrokenCountryBorderException {
        defineAllAreaMembers();
        for (City member : members.values()) {
            defineCityCapital(member);
            defineCityNeighbors(member);
        }
    }

    private void defineCityCapital(City city) {
        for (Country country2 : countries) {
            city.addCoinsType(country2.getName());
        }
        city.addStartCapital();
    }

    private void defineAllAreaMembers()
            throws BrokenCountryBorderException {
        for (Country country : countries) {
            for (City city : country.getCities()) {
                if (members.get(city.getPosition()) == null) {
                    members.put(city.getPosition(), city);
                } else {
                    throw new BrokenCountryBorderException();
                }
            }
        }
    }

    private void defineCityNeighbors(City city) {
        if (members.get(city.getPosition().getPositionDown()) != null) {
            city.addNeighbor(members.get(city.getPosition().getPositionDown()));
        }
        if (members.get(city.getPosition().getPositionUp()) != null) {
            city.addNeighbor(members.get(city.getPosition().getPositionUp()));
        }
        if (members.get(city.getPosition().getPositionLeft()) != null) {
            city.addNeighbor(members.get(city.getPosition().getPositionLeft()));
        }
        if (members.get(city.getPosition().getPositionRight()) != null) {
            city.addNeighbor(members.get(city.getPosition().getPositionRight()));
        }
    }

    /**
     * Check if country location fits to current area.
     *
     * @param country - value of <code>Country</code> type location of which
     * need to be checked.
     * @return <code>true</code> if location of country is above area borders.
     */
    public boolean dontFitAreaBorders(Country country) {
        return country.getCountryArea().getDownLeftPoint().getX() < AREA_START_POINT_BY_LENGTH
                || country.getCountryArea().getDownLeftPoint().getY() < AREA_START_POINT_BY_HEIGHT
                || country.getCountryArea().getDownLeftPoint().getY() > AREA_END_POINT_BY_HEIGHT
                || country.getCountryArea().getDownLeftPoint().getX() > AREA_END_POINT_BY_LENGTH
                || country.getCountryArea().getUpRightPoint().getX() < AREA_START_POINT_BY_LENGTH
                || country.getCountryArea().getUpRightPoint().getY() < AREA_START_POINT_BY_HEIGHT
                || country.getCountryArea().getUpRightPoint().getY() > AREA_END_POINT_BY_HEIGHT
                || country.getCountryArea().getUpRightPoint().getX() > AREA_END_POINT_BY_LENGTH;
    }

    public boolean isFinish() {
        return finish;
    }

    /**
     * Method check if all area countries already end dissemination of all types
     * of coins, and set finish marker if they do.
     *
     * @return <code>true</code> if all countries of area finished dissemination.
     */
    public boolean finishDissemination() {
        for (Country country : countries) {
            if (!country.isFinish()) {
                return false;
            }
        }
        finish = true;
        return true;
    }

    /**
     * Method return results of current simulation.
     *
     * @return <code>Map</code> object with country name as a <code>kay</code>
     *      and value of iterations as a <code>value</code>.
     */
    public Map<String, Integer> getResults() {
        return results;
    }
}
